# python打印内容
# print('徐传良')

# 直接运行python文件，在文件目录中使用puthon xxx.py

# 读取控制台输入
# i = input()
# print(i)

# python语法采用缩进方式，当语句以:结尾时，缩进的语句视为语句块
# a = 100
# if a>= 0:
#     print(a)
# else:
#     print(-a)

# 字符串使用单引号/双引号表示，\表示转义

# python中使用True、Flase表示布尔值，布尔值可以使用and、or、not运算
# python中使用None表示空值，
# python中变量可以反复赋值

# python使用ord()函数获取字符的整数表示，chr()函数把编码转换对应的字符
# print(ord('A'))
# print(ord('中'))
# print(chr(ord('A')))
# print(chr(ord('中')))

# python对bytes类型的数据用自带的b前缀的单引号或双引号来表示
# x = b'ABC'
# print(x)

# unicode表示的字符串通过encode()方法可以编码为bytes
# print('ABC'.encode('ascii'))
# print('ABC'.encode('utf-8'))
# 把byte变为字符串使用decode()方法
# print(b'ABC'.decode('ascii'))
# 转换的bytes中如果只有一小部分无效的字节，可以传入errors='ignore'来忽略错误的字节
# print(b'\xe4\xb8\xad\xff'.decode('utf-8'))
# print(b'\xe4\xb8\xad\xff'.decode('utf-8',errors='ignore'))

# 格式化输出，%d整数 %f浮点数 %s字符串 %x 十六进制数，如果不确定用什么，%s永远起作用，可以把任何数据类型专为字符串

# 使用list和tuple
# names = ['a' 'b' 'c']
# print(names)
# len()函数可以获取list的元素个数
# print(len(names))
# 使用索引访问，正向索引从0开始
# print(names[0],names[1],names[2])
# 使用-1访问最后一个元素,-2访问倒数第2个元素，以此类推
# print(names[-1])
# 往list末尾追加元素
# names.append('阿弥陀福哦')
# print(names[-1])
# print(len(names))
# 插入元素到指定位置，使用索引
# names.insert(2,'第三个')
# print(names)
# 删除list末尾的元素使用pop()
# names.pop()
# print(names)
# 删除指定位置的元素，使用索引
# names.pop(2)
# print(names)
# 替换元素，直接赋值给定索引的元素即可
# names[1] = '替换'
# print(names)
# list中使用不同类型的元素
# names = ['徐传良',12,12.32,True]
# print(names)
# list元素也可以是另外一个list
# names = ['python','java',['asp','php'],'scheme']

# tuple 是另外一种有序列表，与list非常相似，但是tuple一旦被初始化就不能被修改，不可变的，获取元素的方式和list一致，只不过没有增删改
# name = ('java','php','python')
# print(name[1])

# 条件判断，使用:表示缩进，if elseif else
# age = 16
# if age >= 18:
#     print('你是成年人，年龄是',age)
#     print('游戏结束')
# elif age >= 15:
#     print('你是新的成年人，年龄是',age)
#     print('游戏结束')
# else:
#     print('你是未成年人，年龄是',age)
#     print('游戏结束')
# if可以简写，如果是非零数值、非空字符串、非空list则会判定为true
# if age:
#     print('true')
# else:
#     print('false')
# python使用int()函数完成字符串向数字的转换
# i = input('请输入你的年龄')
# ii = int(i)
# if ii:
#     print('true')
# else:
#     print('false')

# 模式匹配，使用match代替if elif elif elif else
# score = 'F'
# if score == 'A':
#     print('score is A')
# elif score == 'B':
#     print('score is B')
# elif score == 'C':
#     print('score is C')
# elif score == 'D':
#     print('score is D')
# else:
#     print('全班倒数第一')

# 循环，使用for...in循环，依次把list或者tuple中的每个元素迭代出来
# names = ['php', 'java', 'C', 'C++']
# for x in names:
#     print(x)
# sum = 0
# for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
#     sum = sum + x
# print(sum)
# range()函数可以生成一个序列，list函数可以抓换成list，比如range(5)生成序列从0开始小于5的整数
# print(list(range(5)))
# 循环，使用while循环，只要条件满足就不断循环
# sum = 0
# n = 99
# while n > 0:
#     sum = sum + n
#     n = n - 2
# print(sum)
# break语句用来提前退出循环
# for x in [0,1,2,3,4,5,6,7,8,9]:
#     if x==5:
#         break
#     else:
#         print(x)
# print('输出结束')
# 使用continue跳过本次循环
# for x in [0,1,2,3,4,5,6,7,8,9]:
#     if x==5:
#         continue
#     else:
#         print(x)
# print('输出结束')

# 字典，python内置了字典dict，代表dictionary，在其他语言中也称为map，使用键值对存储，具有极快的查找速度
# d = {'python': 91, 'java': 80, 'php': 12, 'c': 100}
# print(d['python'])
# 使用赋值的方式将键值对存入字典
# d['c++'] = 89
# d['golang'] = 16
# print(d['golang'])
# 直接使用d['xx']的方式获取value时，如果key不存在会报错，要避免报错有两种方式，一种是用in判断key是否存在
# if 'java' in d:
#     print(d['java'])
# 另外一种方式使用get方法，key不存在会返回none或者自己提供的默认值
# print(d.get('java',-1))
# print(d.get('sss',-1))
# 删除key，使用pop(key)方法，对应的value也会删除
# d.pop('java')
# print(d)

# set 和dict类似，是一组key的集合，不能重复，与java中的set类似，要创建一个set，需要提供一个list作为输入集合，set会自动过滤重复的key
# s = set([1, 2, 3, 1])
# 可以通过add方法添加元素到set中，可以通过remove()方法删除元素
# s.add(1)
# s.remove(1)
# set可以看成数学意义上的无语和无重复元素的集合，两个set之间可以直接做交集、并集操作
# s2 = set([4, 3, 5, 7, 8])
# print(s & s2)
# print(s | s2)
# list的排序方法
# l = ['b','a','c']
# l.sort()
# print(l)

# 函数
# 求绝对值的函数abs()
# print(abs(-100))
# 返回最大值的函数max()
# print(max(1,59,23))
# 数据类型转换函数：int() float() str() bool()
# print(bool('true'))
# print(float('1.23'))
# print(int('12'))
# 函数名其实就是指向一个函数对象的引用，完全可以把函数名赋给一个变量，相当于给这个函数名起了一个别名
# a = abs
# print(a(-100))

# 定义函数，Python中，定义一个函数使用def语句，依次写出函数名、括号、括号中的参数和冒号:，然后，在缩进块中编写函数体，函数的返回值使用return语句返回
# def my_abc(x):
#     if x > 0:
#         return x
#     else :
#         return -x
# print(my_abc(-100))
# print(my_abc(100))
# 函数中如果没有return语句，函数执行完毕后也会有返回结果，只是结果为None，return None可以简写为return
# 如果想定义一个什么事情也不做的空函数，可以使用pass语句，pass可以用来当做占位符，比如还没想好怎么写的函数的代码，就可以先放一个pass，让代码能运行起来
# def nop():
#     pass
# 数据类型检查可以使用内置的isinstance()来实现
# def my_abc(x):
#     if not isinstance(x,(int,float)):
#         raise TypeError('bad operand type')
#     if x>0:
#         return x
#     else:
#         return -x
# print(my_abc('a'))
# print(my_abc(10))
# 函数返回多个值，import math表示导入math包，允许后序的代码使用math包中的sin、cos函数
# import math
#
#
# def move(x, y, step, angle=0):
#     nx = x + step * math.cos(angle)
#     ny = y - step * math.sin(angle)
#     return nx, ny
#
# x,y = move(100,100,60,math.pi/6)
# z = move(100,100,60,math.pi/6)
# print(x,y)
# print(z)

# 函数的参数，Python的函数除了可以定义必选参数外，还可以使用默认参数、可变参数和关键字参数
# 函数的必选参数在前，默认参数在后，否则Python的解释器会报错
# 当函数有多个参数时，把变化大的参数放在前面，变化小的参数放在后面，变化小的参数可以作为默认参数
# def power(x, n=2):
#     s = 1
#     while n > 0:
#        n = n-1
#        s = s*x
#     return s
# print(power(5))
# print(power(5,3))
# 当有多个默认参数时，调用的时候，既可以按照顺序提供默认参数，如test('xcl',12,'女')，意思是除了name，gender这两个参数外，最后一个应用参数在gender上，city由于没有提供仍然使用默认值
# 也可以不按照顺序提供部分默认参数，当不按照顺序提供部分默认参数时，需要把参数名字写上，如test('xcl',22,city='北京')，gender由于没有提供值仍然使用的是男，city使用指定值
# def test(name,age,gender='男',city='杭州'):
#     print(name)
#     print(age)
#     print(gender)
#     print(city)
# test('xcl',12)
# test('xcl',12,'女')
# test('xcl',12,'女','北京')
# test('xcl',22,city='北京')
# 默认参数需要指向不变对象
# Python函数在定义的时候，默认参数L的值就被计算出来了，即[]，因为默认参数L也是一个变量，它指向对象[]，每次调用函数，如果改变了L的内容，则下次调用时，默认参数的内容就变了，不再是函数定义时的[]了
# 要修改下面的情况，可以使用None这个不变对象来实现
# def add_end(L=[]):
#     L.append('END')
#     return L
# print(add_end([1,2,3])) # 输出结果是[1, 2, 3, 'END']
# print(add_end()) # 输出结果是['END']
# print(add_end()) # 输出结果是['END', 'END']
# def add_end(L=None):
#     if L is None:
#         L = []
#     L.append('END')
#     return L
# print(add_end())
# print(add_end())
# print(add_end())

# 可变参数，如果有多个可变参数，手动组装list或者tuple调用函数太麻烦，可以使用可变参数，可变参数的表示方式是在参数前加*号，在函数内部接收的是一个tuple
# 使用list
# def calc(numbers):
#     sum = 0
#     for n in numbers:
#         sum = sum + n * n
#     return sum
# print(calc([1,2,3,4,5]))
# 使用可变参数
# def calc(*numbers):
#     sum = 0
#     for n in numbers:
#         sum = sum + n * n
#     return sum
# print(calc(1,2,3,4,5,6))
# Python中允许在list或者tuple前加一个*号，把list或tuple的元素变成可变参数传进去，*num表示把num这个list的所有元素作为可变参数传进去，这种写法非常常见和有用
# num = [1,2,3,4,5,6]
# print(calc(*num))

# 关键字参数，关键字参数允许传入0个或任意个含参数名的参数，这些关键字参数在函数内部自动组装成为一个dict
# def person(name,age,**kw):
#     print('name:',name,'age:',age,'other:',kw)
# person('xcl',30)
# person('xcl',30,gender='男')
# 可以使用** 将dict的所有key-value用关键字参数传入函数的关键字参数中
# extra = {'city':'beijing','job':'engineer'}
# person('xcl',30,**extra)

# 命名关键字参数，如果要限制关键字参数的名字，就可以使用命名关键字参数，例如只接受city和job作为关键字参数，可以用以下方式定义函数，命名关键字参数需要一个特殊的分隔符*，*后面的参数被视为命名关键字参数
# def person(name,age,*,city,job):
#     print(name,age,city,job)
# person('xcl',30,city='huaibei',job='eng')
# 如果函数中已经有了一个可变参数，后面跟着的命名关键字参数就不再需要一个分隔符*了，命名关键字参数必须传入参数名，这和位置参数不同，命名关键字参数可以有缺省值
# def person(name,age,*args,city,job):
#     print(name,age,args,city,job)
# L=[1,2,3]
# person('xcl',30,*L,city='huaibei',job='eng')

# 参数组合，在Python中定义函数，可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数，这五种参数可以组合使用，蚕食参数定义的顺序必须是必选参数、默认参数、可变参数、命名关键字参数、关键字参数
# def f1(a,b,c=0,*args,**kw):
#     print('a=',a,'b=',b,'c=',c,'args=',args,'kw=',kw)
# def f2(a,b,c=0,*,d,**kw):
#     print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
# 对于任意函数，都可以通过类似func(*args,**kw)的形式来调用它，无论它的参数是如何定义的
# def fun(*args,**kw):
#     print('args:',args,'kw:',kw)
# args=[1,2,3,4]
# kw={'d':99,'x':'df'}
# fun(*args,**kw)

# 递归
# def fun(n):
#     if (n == 1):
#         return 1
#     else:
#         return n * fun(n - 1)
# print(fun(10))

# 切片，对于经常指定索引范围的操作，用循环十分繁琐，Python提供了切片Slice操作
# L = [0,1,2,3,4,5,6,7,8]
# 取前三个元素，一下表示从索引第0个元素开始取到3为止，包头不包尾，如果是从第0个元素取，可以省略0，支持通过负数来倒数取元素
# print(L[:3])
# print(L[0:3])
# print(L[1:3])
# print(L[-2:])
# print(L[-5:-1])
# 创建连续数字元素可以使用range函数
# L=list(range(100))
# print(L)
# 取所有数
# print(L[:])
# 取前十个数
# print(L[:10])
# 取后十个数
# print(L[-10:])
# 取前11-20个数
# print(L[11:20])
# 前10个数，每2个取一个
# print(L[:10:2])
# 所有数，每5个取一个
# print(L[::5])
# list切片后会产生一个新的list，tuple是不可变的list，也支持切片操作，只是操作结果仍是tuple
# 字符串也可以看成是一种list，每个元素就是一个字符，因此字符串也支持切片操作
# s = '0123456789'
# 输出s字符串
# print(s)
# 输出s字符串
# print(s[:])
# 输出s字符串的前三个字符
# print(s[:3])
# 输出s字符串的前6个字符中每2个取一个
# print(s[:6:3])
# 输出字符串每三个取一个
# print(s[::3])

# 迭代，在Python中迭代是通过for...in来完成，只要是可迭代对象，无论有无下表，都可以迭代，比如dict就可以迭代，注意dict迭代默认迭代的是key，如果要迭代value使用for value in d.values()，如果要同时迭代key和value，使用for k,v in d.items()
# 字符串也是可迭代对象，也可以使用for循环迭代
# L = list(range(100))
# for i in L:
#     print(i)
# D = {'a': 1, 'b': 2, 'c': 3}
# for k in D:
#     print(k)
# for v in D.values():
#     print(v)
# for k,v in D.items():
#     print('key:',k,'value:',v)
# S = '代，在Python中迭代是通过for...in来完成，只要是可迭代对象，无论有无下表，都可以迭代，比如di'
# for i in S:
#     print(i)
# 使用for循环时，只要作用于一个可迭代对象，for循环就可以正常运行，判断一个对象是否是可迭代对象，通过collections.abc模块的Iterable类型判断
# from collections.abc import Iterable
# print(isinstance('abc',Iterable)) # 字符串是否可迭代
# print(isinstance([1,2,3],Iterable)) # list是否可迭代
# print(isinstance(123,Iterable)) # 整数是否可迭代
# Python中如果要对list实现类似java那样的下标循环怎么办？Python中内置的enumerate函数可以把一个list变成索引-元素对，这样就可以在for循环中同时迭代索引和元素本身：
# L = list(range(100))
# for i,value in enumerate(L):
#     print(i,value)
# for循环中，同时引用两个变量，在Python中是很常见的，如下：
# for x,y in [(1,1),(2,4),(3,9)]:
#     print(x,y)

# 列表生成式
# 生成1~11的序列
# l = list(range(1, 11))
# 生成[1*1,2*2...10*10]的序列
# 笨方法
# l = []
# for x in range(1, 11):
#     l.append(x * x)
# print(l)
# 使用列表生成式的方法，写列表生成式时，把要生成的元素x*x放在前面，后面跟for循环，就可以把list创建出来
# l = [x * x for x in range(1, 11)]
# print(l)
# l = [x + x for x in range(23, 100)]
# print(l)
# 列表生成式中for循环后面还可以加上if判断，例如筛选只是偶数的数据计算平方
# l = [x * x for x in range(1, 11) if x % 2 == 0]
# print(l)
# 还可以使用两层循环，可以生成全排列，三层以上的循环基本很少用
# l = [m + n for m in 'ABC' for n in 'XYZ']
# print(l)
# 运用列表生成式，可以写出非常简洁的代码。例如，列出当前目录下所有文件和目录名，可以通过一行代码实现
# import os
# l = [d for d in os.listdir('.')]
# print(l)
# for循环其实可以同时使用两个甚至多个变量，比如dict的items()可以同时迭代key和value，因此，列表生成式也可以使用两个变量来生成list
# d = {'x': 'A', 'y': 'B', 'z': 'C' }
# for k,v in d.items():
#     print(k,'=',v)
# l = [k+'='+v for k,v in d.items()]
# print(l)
# 把一个list中所有的字符串变成小写
# L = ['Hello', 'World', 'IBM', 'Apple']
# l = [s.lower() for s in L]
# print(l)
# 列表生成式的if...else问题
# 当if跟在for后面时，if是一个筛选条件，不能带else，表示对for中的x进行过滤，满足条件的x会被过滤出来
# 下方的执行过程可以分为这样几步：1.for x in range()；2.if x是偶数；3.x*x作为list中的一个元素
# l = [x * x for x in range(1, 10) if x % 2 == 0]
# print(l)
# if写在for前面必须加else，因为for前面的部分是一个表达式，它必须根据x计算出一个结果，将这个结果放入list中
# 下方执行过程可以分为这样几步：1.for x in range(1,11)；2.if x是偶数，则将x作为一个元素放入list；3.否则将-x作为一个元素放入list
# l = [x if x % 2 == 0 else -x for x in range(1,11)]
# print(l)

# 生成器，生成器的列表元素是按照某种算法推算出来的，这样不必创建完整的list，从而节省大量的空间，生成器保存的是算法，每次调用next就计算出下一个元素的值
# 定义一个列表生成式
# L = [x * x for x in range(10)]
# 定义一个生成器，只需要把列表生成式的[]改为()，就创建了一个生成器
# g = (x * x for x in range(10))
# 使用next()打印生成器的元素
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# print(next(g))
# 生成器可以使用for循环，因为生成器也是可迭代对象，一般不会用到next，都是通过for循环迭代，生成器保存的是算法
# for n in g:
#     print(n)
# 使用函数生成器
# 斐波拉契数列函数
# def fib(max):
#     n,a,b = 0,0,1
#     while n < max:
#         print(b)
#         # a,b = b,a+b 相当于
#         # t = (b,a+b) t是一个tuple
#         # a = t[0]
#         # b = t[1]
#         a,b = b,a+b
#         n = n+1
#     return 'done'
# fib(5)
# 把上方的函数改成生成器函数只需要把print(b)改成yield b就可以了
# def fib_generator(max):
#     n,a,b = 0,0,1
#     while n<max:
#         yield b
#         a,b = b,a+b
#         n = n+1
#     return 'done'
# f =  fib_generator(10)
# for n in f:
#     print(n)
# 如果一个函数定义中包含yield关键字，那么这个函数就不再是一个普通函数，而是一个生成器（generator）函数，调用一个generator函数将返回一个generator
# generator函数和普通函数执行流程不一样，普通函数执行是顺序执行，遇到return语句或者最后一行函数语句就返回，而变成generator的函数，在每次调用next()的时候执行，遇到yield语句返回，再次执行时从上次返回的yield语句处继续执行
# 下方举一个yield的例子，定义一个函数名为odd的generator函数
# def odd():
#     print('step 1')
#     yield 1
#     print('step 2')
#     yield 2
#     print('step 3')
#     yield 3
# 调用该generator函数时，首先要生成一个generator对象
# o = odd()
# print(next(o))
# print(next(o))
# print(next(o))
# next(o)
# 通过上面执行结果可以看到，odd不是普通函数，是一个generator函数，在执行过程中，遇到yield就中断，下次有继续执行。执行3次yield后，已经没有yield可以执行了，所以第四次调用就报错
# 注意：调用generator函数会创建一个generator对象，多次调用generator函数会创建多个相互独立的generator
# 如何获取generator的return返回值？使用for循环调用generator时，无法通过正常途径获取return的返回值，如果需要获取返回值只能通过捕获StopIteration错误，返回值包含在StopIteration的value中

# 迭代器
# 可以直接作用于for循环的数据类型有以下几种：一类是集合数据类型，如list、tuple、dict、set、str等；一类是generator，包括生成器和带yield的generator function；这些可以直接作用与for循环的对象统称为可迭代对象：Iterable
# 可以使用isinstance()判断一个对象是否是Iterable对象
# from collections.abc import Iterable
# print(isinstance([],Iterable))
# print(isinstance({},Iterable))
# print(isinstance('abc',Iterable))
# print(isinstance((x for x in range(10)),Iterable))
# print(isinstance(100,Iterable))
# 生成器不但可以作用于for循环，还可以被next()函数不断调用并返回下一个值，直到最后抛出StopIteration错误表示无法继续返回下一个值了。可以被next()函数调用并不断返回下一个值的对象称为迭代器：Iterator，可以使用isinstance()判断一个对象是否是Iterato对象
# from collections.abc import Iterator
# print(isinstance((x for x in range(10)),Iterator))
# print(isinstance([],Iterator))
# print(isinstance({},Iterator))
# print(isinstance('abc',Iterator))
# 生成器都是Iterator对象，但list、dict、str虽然是Iterable，却不是Iterator，把list、dict、str等Iterable变成Iterator可以使用iter()函数
# from collections.abc import Iterator
# print(isinstance(iter([]),Iterator))
# print(isinstance(iter('abc'),Iterator))
# 在Python的Iterator对象表示的是一个数据流，Iterator对象可以被next()函数调用并不断返回下一个数据，直到没有数据时抛出StropIteration错误。可以把这个数据流看做是一个有序序列，但我们却不能提前知道序列的长度，只能不断通过next()函数实现按需计算下一个数据，所以Iterator的计算是惰性的，只有在需要返回下一个数据时它才会计算
# Iterator甚至可以表示一个无限大的数据流，而使用list是永远不可能存储全体自然数的

# 函数式编程：函数式编程就是一种抽象程度很高的编程范式，纯粹的函数式编程语言编写的函数没有变量
# 函数本身也可以赋值给变量，即变量可以指向函数，如果一个变量指向了一个函数，那么可以通过该变量来调用这个函数，函数名其实就是指向函数的变量
# 既然变量可以指向函数，函数的参数能接收变量，那么一个函数就可以接收另一个函数作为参数，这种函数就称为高阶函数，如下
# def add(x, y, f):
#     return f(x) + f(y)
# print(add(-4,5,abs))

# map/reduce
# map()函数接收两个参数，一个是函数，一个是Iterable，map将传入的函数依次作用到序列的每个元素，并把结果作为新的Iterator返回，例如：
# def f(x):
#     return x * x
# r = map(f,list(range(100)))
# for n in r:
#     print(n)
# print(list(r))
# map()作为高阶函数，事实上它把运算规则抽象了，因此不但可以计算简单的f(x)=x*x，还可以计算任意复杂的函数，例如下面把list所有数字转为字符串
# print(list(map(str,list(range(2,100)))))
# reduce把一个函数作用在一个序列上，这个函数必须接收两个参数，reduce把结果继续和序列的下一个元素做积累计算，其效果就是：reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
# 例如下方累加集合中的元素
# from functools import reduce
# def add(x,y):
#     return x+y
# print(reduce(add,list(range(10))))
# 如果把序列[1，3，5，7，9]变成整数，也可以使用reduce
# from functools import reduce
# def fn(x, y):
#     return x * 10 + y
# print(reduce(fn,[2,4,5,6,1]))
# 使用map以及reduce函数编写一个str2int的函数
# from functools import reduce
# def fn(x,y):
#     return x*10+y
# def char2num(x):
#     digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
#     return digits[x]
# print(reduce(fn,map(char2num,'1356')))
from functools import reduce


# DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
# def str2int(s):
#     def fn(x,y):
#         return x*10+y
#     def char2num(s):
#         return DIGITS[s]
#     return reduce(fn,map(char2num,s))
# 上方函数还可以使用lambda函数进一步简化
# from functools import reduce
# DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
# def char2num(s):
#     return DIGITS[s]
# def str2int(s):
#     return reduce(lambda x,y:x*10+y,map(char2num,s))

# filter
# filter()函数用于过滤序列，和map()类似，也接收一个函数和一个序列，和map()不同的是，filter()把传入的函数依次作用与每个元素，然后根据返回值是ture/false决定保留还是丢弃元素
# 下方函数只删除偶数，保留奇数
# def is_odd(x):
#     return x % 2 == 1
# print(list(filter(is_odd,list(range(20)))))
# filter()函数返回的是一个Iterator，也就是一个惰性序列，所以要强迫filter()完成计算结果，需要用list()函数获得所有结果并返回list
# 使用filter实现素数的过滤
# 先构造一个从3开始的奇数序列
# def _odd_iter():
#     n = 1
#     while True:
#         n = n+2
#         yield n
# 定义一个筛选函数
# def _not_divisible(n):
#     return lambda x : x % n > 0
# 定义一个生成器，不断的返回下一个素数
# def primes():
# 首先2是一个素数
# yield 2
# 获取生成器
# it = _odd_iter()
# while True:
# 调用生成器获取的第一个元素是3，然后是5、7、等奇数
# n = next(it) # 返回序列的第一个数
# yield n
# it = filter(_not_divisible,it)
# 打印1000以内的素数
# for n in primes():
#     if n<1000:
#         print(n)
#     else:
#         break

# Python内置的sorted()函数用于排序，直接使用可以对list进行排序，还可以接收一个key函数来实现自定义的排序，例如下方按照绝对值排序
# print(sorted([3,54,1,23,56,34],key=abs))
# key指定的函数将作用与list的每一个元素，并根据key函数返回的结果进行排序。
# 例如对字符串序列进行排序
# print(sorted(['bob', 'about', 'Zoo', 'Credit']))
# 如果向忽略大小写进行排序，只需要增加函数即可
# print(sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.lower))
# 如果想反向排序，也不需要改动key，增加第三个参数reverse=true即可
# print(sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.lower,reverse=True))

# 返回函数：函数作为返回值，高阶函数除了可以接受函数作为参数外，还可以把函数作为结果值返回，例如我们想对一个可变参数进行求和，但是不需要立即求和，而是在后面的代码中再计算，如下：
# def lazy_sum(*args):
#     def sum():
#         ax = 0
#         for n in args:
#             ax = ax+n
#         return ax
#     return sum
# 当调用lazy_sum()时，返回的并不是求和结果，而是求和函数：
# f = lazy_sum(1,2,3,4,5,6,7,8)
# 此时得到的只是一个函数
# print(f)
# 只有真正调用函数时，才会求和
# print(f())
# 上方的例子中，在函数中又定义了函数，内部函数可以引用外部函数的参数和局部变量，当lazy_sum返回函数sum时，相关参数和变量都保存在返回的函数中，这种称为闭包；lazy_sum()被调用时，每次调用都会返回一个新的函数
# 闭包要注意，返回函数不要引用任何循环变量，或者后序会发生变化的变量
# 使用闭包时，就是内层函数引用了外层函数的局部变量，如果只是读外层函数变量的值，则调用一切正常；但是如果对外层变量赋值，由于Python会把外层变量当成内层函数的局部变量，会报错，原因是局部变量没有被初始化，如果想修改外层变量的值，需要在内层函数中加一个nonlocal关键字
# def lazy_sum(*args):
#     x = 0
#     def sum():
#         nonlocal x
#         for n in args:
#             x = x + n
#         return x
#     return sum
# l = lazy_sum(*list(range(10)))
# print(l())

# 匿名函数：关键字lambda表示匿名函数，冒号前面是函数参数，匿名函数有个限制，就是只能有一个表达式，不用写return，返回值就是该表达式的结果，匿名函数也是一个函数对象，也可以把匿名函数赋值给一个变量，再利用变量调用该函数
# print(list(map(lambda x:x*x*x,list(range(200)))))

# 装饰器，函数对象有一个__name__属性，可以拿到函数名称，本质上装饰器（decorator）就是一个返回函数的高阶函数
# 例如打印当前日期的函数now()
# def now():
#     print('20240221')
# f = now
# f()
# 假如我们要增强now()函数的功能，在函数调用前后自动打印日志，但是又不希望修改now()函数的定义，这种在代码运行期间动态增加功能的方式，称之为装饰器
# def log(func):
#     def wrapper(*args,**kw):
#         print('call %s():'% func.__name__)
#         return func(*args,**kw)
#     return wrapper
# 上方的log函数，因为它是一个decorator，所以接受一个函数作为参数，并返回一个函数。我们要借助Python的@语法，把decorator置于函数的定义处：
# @log
# def now():
#     print('20240221')
# 此时调用now()函数，不仅会运行now()函数，还会运行now()函数前打印一行日志，把@log放到now()函数的定义处，相当于执行了语句now = log(now)，由于log()是一个decoder，返回一个函数，所以，原来的now()函数仍然存在，只是现在同名的now变量指向了新的函数，
# 于是调用now()将执行新函数，即在log()函数中返回的wrapper()函数
# wrapper()函数的参数定义是(*args,**kw)，因此，wrapper()函数可以接受任意参数的调用，在wrapper函数内，首先打印日志，再紧接着调用原始函数
# 如果decorator本身需要传入参数，那就需要编写一个返回decorator的高阶函数，写出来会更复杂
# now()
# 比如上方例子中要定义log的文本
# import functools
# def log(text):
#     def decorator(func):
#         @functools.wraps(func)
#         def wrapper(*args,**kw):
#             print('%s %s():' % (text,func.__name__))
#             return func(*args,**kw)
#         return wrapper
#     return decorator
# @log('execute')
# def now():
#     print('20240221')
# now()
# 三层嵌套的效果是：now = log('execute')(now)

# 偏函数，Python的functools模块提供了很多有用的功能，其中一个就是偏函数（Partial function），偏函数的作用就是简化函数的参数操作，当函数的某个参数是我们提前获知的，就可以通过偏函数把它固定住，如下
# 定义一个取余函数，默认对2取余
# def mod(x,y=2):
#     return x % y == 0
# 假设要对3、对4取余，每次调用mod()函数时都要写上y的值，如下：
# print(mod(4,3))
# print(mod(5,3))
# 使用偏函数固定参数
# import functools
# mod_3 = functools.partial(mod,y=3)
# print(mod_3(4))
# print(mod_3(5))

# 模块 在Python中，一个.py文件就称之为一个模块，Python引入目录来组织模块，称为包，每个包目录下都会有一个__init__.py文件，这个文件是必须存在的，否则Python就把这个目录当成普通目录，而不是一个包
# 注意，自己创建模块时要注意命名，不能和Python自带的模块名称冲突。例如，系统自带了sys模块，自己的模块就不可命名为sys.py，否则将无法导入系统自带的sys模块。
# __init__.py本身就是一个模块，它的模块名就是它所处的包名

# 使用模块，使用Python内置的sys模块
# 任何模块的第一个字符串都被视为模块的文档注释
# 使用sys模块的第一步就是导入sys模块，导入sys模块有，就有了变量sys指向该模块，利用sys变量可以访问sys模块的所有功能
# sys模块有一个argv变量，用list存储了命令行的所有参数，argv至少有一个元素，因为第一个参数永远是该.py文件的名称
# 'a test module'
# __author__ = 'xcl'
# import sys
# def test():
#     args = sys.argv
#     print(args[0])
#     if len(args)==1:
#         print('hello world')
#     elif len(args)==2:
#         print('hello,%s' % args[1])
#     else:
#         print('too many arguments')
#
# if __name__=='__main__':
#     test()
# 上方代码说明：当我们在命令行运行当前Python模块文件时，Python解释器把一个特殊变量__name__置为__main__，而如果在其他地方导入当前模块时，则if会判断失效，这种方式可以让一个模块通过命令运行时执行一些额外的代码，最常见的就是运行测试
# 作用域
# 在一个模块中，会定义很多函数和变量，但是有个函数和变量我们希望给别人使用，有个函数和变量希望仅仅在模块内部使用，在Python中，是通过_前缀来实现的
# 正常的函数和变量名是公开的，可以被直接引用，如abc，x123等
# 类似__xxx__这样的变量是特殊变量，可以被直接引用，但是有特殊用途，自己的变量一般不要使用这种命名方式
# 类似_xxx和__xxx这样的函数或变量就是非公开的（private），不应该被引用，如_abc/__abc等

# 安装第三方模块
# 在Python中，安装第三方模块是通过包管理工具pip完成的，一般来说第三方库都会在Python官网的pypi.python.org网站注册，要安装第三方库可以在官网或者pypi上搜索。使用pip install xxx来安装第三方库
# 安装常用模块
# 在使用Python时，我们经常需要用到很多第三方库，使用pip一个一个安装费时费力，还需要考虑兼容性，推荐直接使用Anaconda，这是一个基于Python的数据处理和科学计算平台，已经内置了非常多有用的第三方库，装上Anaconda，就相当于把数十个第三方模块自动安装好了。
# 当试图加载一个模块时，Python会在指定路径下搜索对应的.py文件，如果找不到就会报错。默认情况下解释器会搜索当前目录、所有已安装的内置模块和第三方模块，搜索路径存放在sys模块的path变量中，
# 如果我们要添加自己的搜索目录，有两种方法：
# 一种方法是直接修改sys.path，添加要搜索的目录，这种方法是在运行时修改，运行结束后失效：
# import sys
# sys.path.append('D:/code/python/pythonProject/0222/testpackage')
# 第二种方法是设置环境变量PYTHONPATH，该环境变量的内容会被自动添加到门模块搜索路径中，设置方式与设置path环境变量类似，注意只需要添加要搜索的路径即可，Python自己本身搜索的路径不受影响

# 面向对象编程
# class Student(object):
#     def __init__(self,name,score):
#         self.name = name
#         self.score = score
#
#     def print_score(self):
#         print('%s %s' % (self.name,self.score))
#
# bart = Student('bart',59)
# lisa = Student('lisa',87)
# bart.print_score()
# lisa.print_score()

# 类和实例
# Python中定义类通过class关键字，class后面紧接着是类名，类名通常大写开头的单词，紧接着是(object)，表示该类是从哪个类继承下来的，如果没有合适的继承类，就是用object类，这是所有类最终都会继承的类，创建类的实例是通过类名+()实现的
# 由于类可以起到模板的作用，因此，可以在创建实例的时候，把一些我们认为必须绑定的属性强制填写进度，通过定义一个特殊的__init__方法，在创建实例的时候，就把属性绑上去
# __init__方法的第一个参数永远是self，表示创建的实例本身，因此，在__init__方法内部，就可以把各种属性绑定到self，因为sefl就指向创建的实例本身
# 有了__init__方法，在创建实例的时候，就不能传入空的参数了，必须传入与__init__方法匹配的参数，但self不需要传，Python解释器自己会把实例变量传进去
# 与普通函数相比，在类中定义的函数只有一点不同，就是第一个参数永远是实例变量self，并且，调用时，不用传递该参数，除此之外，类的方法和普通函数没有什么区别，所以仍然可以使用默认参数、可变参数、关键字参数和命名关键字参数
# 类的方法除了第一个参数是self外，其他和普通函数一样，要调用一个方法，只需要在实例变量上直接调用，除self不用传递，其他参数正常传入
# 类的内部属性不被外部访问，可以把属性的名称前加上两个下划线__，在Python中，实例的变量名如果以__开头，就变成了一个私有变量，只有内部可以访问，外部不能访问
# class Person(object):
#     def __init__(self,name,age):
#         self.__name = name
#         self.__age = age
#
#     def print(self):
#         print('%s %s' % (self.__name,self.__age))
# someone = Person('jack',23)
# someone.print()
# print(someone._Person__name)
# 在Python中，变量名类似__xxx__的，也就是以双下划线开头，并且以双下划线结尾的，是特殊变量，特殊变量是可以直接访问的，不是private变量
# 有时候会看到以一个下划线开头的实例变量，如_xxx，这样的实例变量外部是可以访问的，但是按照约定俗成的规定，当看到这样的变量时，意思就是虽然可以访问，但是请视为私有变量，不要随意访问

# 继承和多态，只需要在类定义的()中增加需要继承的父类类型即可
# class Animal(object):
#     def __init__(self,type):
#         self.__type = type
#     def run(self):
#         print(self.__type,' running')
#
# class Cat(Animal):
#     pass
# class Dog(Animal):
#     pass
#
# cat = Cat('cat')
# dog = Dog('dog')
# cat.run()
# dog.run()
# 判断一个变量是否是某个类型可以用isinstance()判断
# 对于静态语言（如java）来说，如果需要传入Animal类型，则传入的对象必须是Animal类型或者它的子类，否则，无法调用run()方法
# 但是对于Python这种动态语言来说，则不一定需要传入Animal类型，只需要保证传入的对象有一个run()方法就可以了

# 获取对象信息
# 判断对象类型可以使用type()函数，常见的类型在Python的types模块中有定义
# import types
# if type(123) == types.FunctionType:
#     print('函数类型')
# elif type(123) == int:
#     print('数字类型')
# 对于class的继承关系来说，使用type()很不方便，要判断class类型可以使用isinstance()函数，isinstance()判断的是一个对象是否是该类型本身，或者位于该类型的父继承链上
# class Person(object):
#     pass
# class Man(Person):
#     pass
# class Woman(Person):
#     pass
# person = Person()
# man = Man()
# woman = Woman()
# print(type(person))
# false
# print(type(man) == type(person))
# false
# print(type(woman) == type(person))
# true
# print(isinstance(person,Person))
# true
# print(isinstance(man,Person))
# true
# print(isinstance(woman,Person))
# isinstance()还可以判断一个变量是否是某些类型中的一种
# true
# isinstance([1,2,3],(list,tuple))
# 如果要获得一个对象的所有属性和方法，可以使用dir()函数，它返回一个包含字符串的list
# getattr()、setattr()、hasattr()方法可以操作一个对象的状态，类似于java中的反射
# class Person(object):
#     def __init__(self,name):
#         self.__name = name
#     def name(self):
#         return self.__name
#     def run(self):
#         print('run')
# p = Person('wz')
# print(dir(p))
# 对象是有属性name
# print(hasattr(p,'name'))
# 获取对象的name属性的值，如果不存在该属性则返回404
# print(getattr(p,'name',404))
# 设置对象的name属性为jj
# print(setattr(p,'name','jj'))

# 实例属性和类属性
# 由于Python是动态语言，根据类创建的实例可以任意绑定属性，给实例绑定属性的方法是通过实例变量，或者通过self变量。直接在class中定义属性，这种属性是类属性，归类所有。
# class Student(object):
# 类属性
#     name = 'jack'
# s1 = Student()
# s2 = Student()
# print(s1.name)
# print(s2.name)
# s1.name = 'tom'
# print(s1.name)
# del s1.name
# print(s1.name)

# 面向对象高级编程
# 使用__slots__
# 正常情况下，当我们定义了一个class，创建了一个class的实例后，可以给该实例绑定任何属性和方法
# class Student(object):
#     pass
# s1 = Student()
# s2 = Student()
# 给s1实例绑定一个属性
# s1.name = 'jack'
# print(s1.name)
# 给s1实例绑定一个方法
# def set_age(self,age):
#     self.age = age
# from types import MethodType
# s1.set_age = MethodType(set_age,s1)
# s1.set_age(12)
# print(s1.age)
# 但是给实例绑定的方法，对另一个实例是不起作用的，如果要给所有的实例都绑定方法，可以给class绑定方法
# def set_score(self,score):
#     self.score = score
# Student.set_score = set_score
# s1.set_score(24)
# print(s1.score)
# s2.set_score(24)
# print(s2.score)
# 使用__slots__，Python允许在定义class的时候，定义一个特殊的__slots__变量，来限制class实例能添加的属性，该属性仅对当前类实例起作用，对继承的子类不起作用
# class Student(object):
#     __slots__ = ('name','age')
# s = Student()
# s.name = 'jack'
# s.age = 12
# AttributeError: 'Student' object has no attribute 'gender'
# s.gender = '男'

# @property，把一个getter方法变成属性，只需要加上@property就可以了，此时，@property本身又创建了另一个装饰器@xxx.setter，负责把一个setter方法变成属性赋值，@property注解帮助我们在对实例属性操作的时候，就知道该属性很可能不是直接暴露的，而是通过getter和setter方法来实现的
# class Student(object):
#     @property
#     def score(self):
#         return self._score
#
#     @score.setter
#     def score(self,value):
#         if not isinstance(value,int):
#             raise ValueError('score must be an integer!')
#         if value<0 or value>100:
#             raise ValueError('score must between 0~100')
#         self._score = value
# s = Student()
# s.score=60
# print(s.score)
# s.score = 999
# print(s.score)
# 还可以定义只读属性，只定义getter方法，不定义setter方法就是一个只读属性
# class Student(object):
#     @property
#     def birth(self):
#         return self._birth
#
#     @birth.setter
#     def birth(self,value):
#         self._birth = value
#
#     # age就是一个只读属性
#     @property
#     def age(self):
#         return 2024-self._birth
# 注意，属性的方法名不要和实例的变量重名，否则在对属性赋值的时候，会导致无限递归

# Python中的多继承
# class Fly(object):
#     def fly(self):
#         print('i can fly')
# class Person(object):
#     def say(self):
#         print('i can say something')
# class Man(Person,Fly):
#     pass
# m = Man()
# m.say()
# m.fly()

# 定制类，类的__len__()方法是为了让class作用域len()函数，__slots__可以限制属性，除此之外，Python的class中还有许多这种用途的函数，帮助定制类
# __str__()函数作用类似于java中的toString
# class Student(object):
#     def __init__(self,name):
#         self._name = name
#     def __str__(self):
#         return 'Student object (name: %s)' % self._name
# s = Student('jack')
# print(s)
# __iter__，如果一个类想被用于for...in循环，类似list或tuple那样，就必须实现一个__iter__()方法，该方法返回一个迭代对象，然后，Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值，知道遇到StopIteration错误时退出循环
# class Fib(object):
#     def __init__(self):
        # 初始化两个计数器
        # self.a, self.b = 0, 1

    # def __iter__(self):
        # 实例本身就是迭代对象，故返回自己
        # return self

    # def __next__(self):
        # 计算下一个值
        # self.a, self.b = self.b, self.a + self.b
        # 退出循环条件
        # if self.a > 10000:
        #     raise StopIteration()
        # else:
            # 返回下一个值
            # return self.a

# for n in Fib():
#     print(n)
